home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue51 / ComCorn / VarCache.pas < prev   
Encoding:
Pascal/Delphi Source File  |  1999-10-12  |  8.4 KB  |  324 lines

  1. unit VarCache;
  2.  
  3. interface
  4.  
  5. uses WIndows, Classes, ActiveX;
  6.  
  7. type
  8.   TVariantCache = class;
  9.   
  10.   // Class representing one variant and associated dispatch methods
  11.   TVariantEntry = class(TObject)
  12.   private
  13.     FDispValue: Pointer;
  14.     FRefCount: Integer;
  15.     FDispIds: TStringList;
  16.     FNext: TVariantEntry;
  17.     FPrev: TVariantEntry;
  18.     FOwner: TVariantCache;
  19.   public
  20.     constructor Create(const V: OleVariant; PrevEntry: TVariantEntry;
  21.       AOwner: TVariantCache);
  22.     destructor Destroy; override;
  23.     function GetIdsOfNames(Names: PChar; DispIDs: PDispIDList): Boolean;
  24.   end;
  25.  
  26.   // Class representing a circular doubly-linked list of TVariantEntries
  27.   TVariantCache = class(TObject)
  28.   private
  29.     FVariantEntries: TVariantEntry;
  30.   public
  31.     function Add(const V: OleVariant; Names: PChar; DispIDs: PDispIDList;
  32.       Length: Integer): TVariantEntry;
  33.     procedure Remove(const V: OleVariant);
  34.     function Find(const V: OleVariant): TVariantEntry;
  35.   end;
  36.  
  37. implementation
  38.  
  39. uses ComObj, SysUtils;
  40.  
  41. type
  42.   TDynDispIDList = array of TDispID;
  43.  
  44. const
  45.   MemSize = 6;
  46.  
  47. var
  48.   OldVarClear, NewVarClear: PByte;
  49.   VarClearCode: array[1..MemSize] of Byte;
  50.   VariantCache: TVariantCache;
  51.  
  52. { TVariantEntry }
  53.  
  54. constructor TVariantEntry.Create(const V: OleVariant;
  55.   PrevEntry: TVariantEntry; AOwner: TVariantCache);
  56. begin
  57.   FOwner := AOwner;
  58.   FDispValue := TVarData(V).VDispatch;
  59.   FRefCount := 1;
  60.   FDispIds := TStringList.Create;
  61.   FDispIds.Duplicates := dupError;
  62.   FDispIDs.Sorted := True;
  63.   if PrevEntry <> nil then
  64.   begin
  65.     FPrev := PrevEntry;
  66.     if PrevEntry.FNext = nil then FNext := PrevEntry
  67.     else FNext := PrevEntry.FNext;
  68.     FNext.FPrev := Self;
  69.     PrevEntry.FNext := Self;
  70.   end;
  71. end;
  72.  
  73. destructor TVariantEntry.Destroy;
  74. var
  75.   I: Integer;
  76.   List: TDynDispIdList;
  77. begin
  78.   List := nil;
  79.   for I := FDispIds.Count - 1 downto 0 do
  80.   begin
  81.     TObject(List) := FDispIds.Objects[I];
  82.     List := nil;
  83.   end;
  84.   FDispIDs.Free;
  85.   inherited Destroy;
  86.   if FNext <> nil then
  87.   begin
  88.     if FNext.FNext = Self then
  89.     begin
  90.       FNext.FNext := nil;
  91.       FNext.FPrev := nil;
  92.     end
  93.     else begin
  94.       FNext.FPrev := FPrev;
  95.       FPrev.FNext := FNext;
  96.     end;
  97.     FOwner.FVariantEntries := FNext;
  98.   end
  99.   else
  100.     FOwner.FVariantEntries := nil;
  101. end;
  102.  
  103. function TVariantEntry.GetIdsOfNames(Names: PChar;
  104.   DispIDs: PDispIDList): Boolean;
  105. var
  106.   Index: Integer;
  107.   ArrayObj: TObject;
  108. begin
  109.   Result := FDispIds.Find(Names, Index);
  110.   if Result then
  111.   begin
  112.     ArrayObj := FDispIds.Objects[Index];
  113.     Move(TDynDispIdList(ArrayObj)[0], DispIDs^,
  114.       Length(TDynDispIdList(ArrayObj)) * SizeOf(TDispID));
  115.   end;
  116. end;
  117.  
  118. { TVariantCache }
  119.  
  120. function TVariantCache.Add(const V: OleVariant; Names: PChar;
  121.   DispIDs: PDispIDList; Length: Integer): TVariantEntry;
  122. var
  123.   List: TDynDispIdList;
  124.   Index: Integer;
  125. begin
  126.   Result := Find(V);
  127.   SetLength(List, Length);
  128.   Move(DispIDs^, List[0], Length * SizeOf(TDispID));
  129.   if Result <> nil then
  130.   begin
  131.     Inc(Result.FRefCount);
  132.     if not Result.FDispIds.Find(Names, Index) then
  133.       Result.FDispIds.AddObject(Names, TObject(List));
  134.   end
  135.   else begin
  136.     Result := TVariantEntry.Create(V, FVariantEntries, Self);
  137.     Result.FDispIds.AddObject(Names, TObject(List));
  138.   end;
  139.   Pointer(List) := nil;  // prevent automatic cleanup of dynamic array
  140.   FVariantEntries := Result;
  141. end;
  142.  
  143. function TVariantCache.Find(const V: OleVariant): TVariantEntry;
  144. var
  145.   VarEntry: TVariantEntry;
  146. begin
  147.   Result := nil;
  148.   if FVariantEntries <> nil then
  149.   begin
  150.     VarEntry := FVariantEntries;
  151.     repeat
  152.       if VarEntry.FDispValue = TVarData(V).VDispatch then
  153.       begin
  154.         Result := VarEntry;
  155.         FVariantEntries := Result;
  156.         Exit;
  157.       end;
  158.       VarEntry := VarEntry.FNext;
  159.     until (VarEntry = FVariantEntries) or (VarEntry = nil);
  160.   end;
  161. end;
  162.  
  163. procedure TVariantCache.Remove(const V: OleVariant);
  164. var
  165.   VarEntry: TVariantEntry;
  166. begin
  167.   if TVarData(V).VType = varDispatch then
  168.   begin
  169.     VarEntry := Find(V);
  170.     if VarEntry <> nil then
  171.     begin
  172.       Dec(VarEntry.FRefCount);
  173.       if VarEntry.FRefCount = 0 then VarEntry.Free;
  174.     end;
  175.   end;
  176. end;
  177.  
  178. { Supporting procedures }
  179.  
  180. procedure RemoveVariantFromCache(var V: Variant);
  181. begin
  182.   VariantCache.Remove(V);
  183. end;
  184.  
  185. procedure MyVarClear;
  186. asm
  187.   push eax                      // save registers
  188.   push edx
  189.   mov edx, eax                  // put Variant in edx
  190.   lea eax, VariantCache         // put VariantCache object self in eax
  191.   call TVariantCache.Remove     // call Remove
  192.   mov eax, edx                  // put Variant back in eax
  193.   pop edx
  194.   call System.@VarClear         // do normal variant clearing logic
  195.   pop eax
  196. end;
  197.  
  198. procedure RemapVarClrProc;
  199. var
  200.   JmpInst: array[1..MemSize] of Byte;
  201.   OldProtect: DWORD;
  202.   NewPtr: PByte;
  203. begin
  204.   NewVarClear := @MyVarClear;          // NewFoo holds addr of MyVarClear
  205.   NewPtr := @NewVarClear;              // NewPtr holds addr of NewVarClear
  206.   // set up array containing opcodes for jump...
  207.   JmpInst[1] := $FF;                        // jmp
  208.   JmpInst[2] := $25;                        // dword ptr
  209.   Move(NewPtr, JmpInst[3], SizeOf(NewPtr)); // [NewFoo]
  210.   // Put address of _VarClr into OldVarClear
  211.   asm
  212.     push eax
  213.     mov eax, offset System.@VarClr
  214.     mov OldVarClear, eax
  215.     pop eax
  216.   end;
  217.   // enable read/write/execute permission on _VarClr code
  218.   Win32Check(VirtualProtect(OldVarClear, MemSize, PAGE_EXECUTE_READWRITE,
  219.     @OldProtect));
  220.   // Read old VarClr code
  221.   Move(OldVarClear^, VarClearCode, MemSize);
  222.   // Patch VarClr with new jmp code
  223.   Move(JmpInst, OldVarClear^, MemSize);
  224. end;
  225.  
  226. // GetIDsOfNames wrapper taken from ComObj.pas
  227. procedure GetIDsOfNames(const Dispatch: IDispatch; Names: PChar;
  228.   NameCount: Integer; DispIDs: PDispIDList);
  229.  
  230.   procedure RaiseNameException;
  231.   begin
  232.     raise EOleError.CreateFmt('Method ''%s'' not supported by automation object',
  233.       [Names]);
  234.   end;
  235.  
  236. type
  237.   PNamesArray = ^TNamesArray;
  238.   TNamesArray = array[0..0] of PWideChar;
  239. var
  240.   N, SrcLen, DestLen: Integer;
  241.   Src: PChar;
  242.   Dest: PWideChar;
  243.   NameRefs: PNamesArray;
  244.   StackTop: Pointer;
  245.   Temp: Integer;
  246. begin
  247.   Src := Names;
  248.   N := 0;
  249.   asm
  250.     MOV  StackTop, ESP
  251.     MOV  EAX, NameCount
  252.     INC  EAX
  253.     SHL  EAX, 2  // sizeof pointer = 4
  254.     SUB  ESP, EAX
  255.     LEA  EAX, NameRefs
  256.     MOV  [EAX], ESP
  257.   end;
  258.   repeat
  259.     SrcLen := StrLen(Src);
  260.     DestLen := MultiByteToWideChar(0, 0, Src, SrcLen, nil, 0) + 1;
  261.     asm
  262.       MOV  EAX, DestLen
  263.       ADD  EAX, EAX
  264.       ADD  EAX, 3      // round up to 4 byte boundary
  265.       AND  EAX, not 3
  266.       SUB  ESP, EAX
  267.       LEA  EAX, Dest
  268.       MOV  [EAX], ESP
  269.     end;
  270.     if N = 0 then NameRefs[0] := Dest else NameRefs[NameCount - N] := Dest;
  271.     MultiByteToWideChar(0, 0, Src, SrcLen, Dest, DestLen);
  272.     Dest[DestLen-1] := #0;
  273.     Inc(Src, SrcLen+1);
  274.     Inc(N);
  275.   until N = NameCount;
  276.   Temp := Dispatch.GetIDsOfNames(GUID_NULL, NameRefs, NameCount,
  277.     GetThreadLocale, DispIDs);
  278.   if Temp = Integer(DISP_E_UNKNOWNNAME) then RaiseNameException else OleCheck(Temp);
  279.   asm
  280.     MOV  ESP, StackTop
  281.   end;
  282. end;
  283.  
  284. procedure CachingVarDispInvoke(Result: PVariant; const Instance: Variant;
  285.   CallDesc: PCallDesc; Params: Pointer); cdecl;
  286.  
  287.   procedure RaiseException;
  288.   begin
  289.     raise EOleError.Create('Variant does not reference an automation object');
  290.   end;
  291.  
  292. var
  293.   Dispatch: Pointer;
  294.   DispIDs: array[0..63] of Integer;
  295.   VarEntry: TVariantEntry;
  296.   Names: PChar;
  297.   Count: Integer;
  298. begin
  299.   if TVarData(Instance).VType = varDispatch then
  300.     Dispatch := TVarData(Instance).VDispatch
  301.   else if TVarData(Instance).VType = (varDispatch or varByRef) then
  302.     Dispatch := Pointer(TVarData(Instance).VPointer^)
  303.   else RaiseException;
  304.   Names := @CallDesc^.ArgTypes[CallDesc^.ArgCount];
  305.   Count := CallDesc^.NamedArgCount + 1;
  306.   VarEntry := VariantCache.Find(Instance);
  307.   if (VarEntry = nil) or
  308.     (not VarEntry.GetIdsOfNames(Names, @DispIDs)) then
  309.   begin
  310.     GetIDsOfNames(IDispatch(Dispatch), Names, Count, @DispIDs);
  311.     VariantCache.Add(Instance, Names, @DispIDs, Count);
  312.   end;
  313.   if Result <> nil then VarClear(Result^);
  314.   DispatchInvoke(IDispatch(Dispatch), CallDesc, @DispIDs, @Params, Result);
  315. end;
  316.  
  317. initialization
  318.   VariantCache := TVariantCache.Create;
  319.   RemapVarClrProc;
  320.   VarDispProc := @CachingVarDispInvoke;
  321. finalization
  322.   VariantCache.Free;
  323. end.
  324.